I established a faster "python" midas frontend by instead having a python thread put it's data in a shared memory buffer that the C++ script reads:
import mmap import posix_ipc import numpy as np SHM_NAME = "/my_shared_memory" SEM_NAME = "/my_semaphore" DATA_SIZE = 1024 * 1024 # 1 MB # Create or open shared memory shared_mem = posix_ipc.SharedMemory(SHM_NAME, posix_ipc.O_CREAT, size=DATA_SIZE) memory = mmap.mmap(shared_mem.fd, shared_mem.size) shared_mem.close_fd() # Create or open semaphore semaphore = posix_ipc.Semaphore(SEM_NAME, posix_ipc.O_CREAT, initial_value=0) try: while True: # Generate or retrieve data data = np.random.randint(0, 65535, size=DATA_SIZE // 2, dtype=np.uint16) byte_data = data.tobytes() # Write data to shared memory memory.seek(0) memory.write(byte_data) # Signal the C++ frontend semaphore.release() # Control the data generation rate as needed except KeyboardInterrupt: pass finally: # Clean up memory.close() shared_mem.unlink() semaphore.unlink()
import mmap
import posix_ipc
import numpy as np
SHM_NAME = "/my_shared_memory"
SEM_NAME = "/my_semaphore"
DATA_SIZE = 1024 * 1024 # 1 MB
# Create or open shared memory
shared_mem = posix_ipc.SharedMemory(SHM_NAME, posix_ipc.O_CREAT, size=DATA_SIZE)
memory = mmap.mmap(shared_mem.fd, shared_mem.size)
shared_mem.close_fd()
# Create or open semaphore
semaphore = posix_ipc.Semaphore(SEM_NAME, posix_ipc.O_CREAT, initial_value=0)
try:
while True:
# Generate or retrieve data
data = np.random.randint(0, 65535, size=DATA_SIZE // 2, dtype=np.uint16)
byte_data = data.tobytes()
# Write data to shared memory
memory.seek(0)
memory.write(byte_data)
# Signal the C++ frontend
semaphore.release()
# Control the data generation rate as needed
except KeyboardInterrupt:
pass
finally:
# Clean up
memory.close()
shared_mem.unlink()
semaphore.unlink()
Then the midas frontend is out standard C++ frontend, to eliminate the python slowdown we were seeing:
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #include <iostream> #include <fstream> #include <sstream> #include <vector> #include "midas.h" #include "mfe.h" #include <curl/curl.h> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <semaphore.h> #include <chrono> #include <errno.h> void trigger_update(INT, INT, void*); /*-- Globals -------------------------------------------------------*/ /* The frontend name (client name) as seen by other MIDAS clients */ const char *frontend_name = "DataSimulator"; /* The frontend file name, don't change it */ const char *frontend_file_name = __FILE__; /* frontend_loop is called periodically if this variable is TRUE */ BOOL frontend_call_loop = FALSE; /* a frontend status page is displayed with this frequency in ms */ INT display_period = 0; /* maximum event size produced by this frontend */ INT max_event_size = 1024 * 1024 * 32; /* maximum event size for fragmented events (EQ_FRAGMENTED) */ INT max_event_size_frag = 5 * max_event_size; /* buffer size to hold events */ INT event_buffer_size = 5 * max_event_size; /*-- Shared Memory Configuration --*/ #define SHM_NAME "/my_shared_memory" #define SEM_NAME "/my_semaphore" #define SHM_DATA_SIZE (1024 * 1024) // 1 MB // Shared memory and semaphore handles int shm_fd = -1; void* shm_ptr = nullptr; sem_t* semaphore = nullptr; /*-- Function declarations -----------------------------------------*/ INT frontend_init(void); INT frontend_exit(void); INT begin_of_run(INT run_number, char *error); INT end_of_run(INT run_number, char *error); INT pause_run(INT run_number, char *error); INT resume_run(INT run_number, char *error); INT frontend_loop(void); INT read_trigger_event(char *pevent, INT off); INT poll_event(INT source, INT count, BOOL test); INT interrupt_configure(INT cmd, INT source, POINTER_T adr); /*-- Equipment list ------------------------------------------------*/ BOOL equipment_common_overwrite = TRUE; EQUIPMENT equipment[] = { {"Data Simulator", /* equipment name */ {2, 0, /* event ID, trigger mask */ "SYSTEM", /* event buffer */ EQ_POLLED, /* equipment type */ 0, /* event source */ "MIDAS", /* format */ TRUE, /* enabled */ RO_RUNNING, /* and update ODB */ 100, /* read every 100 ms */ 0, /* stop run after this event limit */ 0, /* number of sub events */ TRUE, /* log history */ "", "", "",}, read_trigger_event /* readout routine */ }, {""} }; /*-- Frontend Init -------------------------------------------------*/ INT frontend_init() { // Open shared memory shm_fd = shm_open(SHM_NAME, O_RDONLY, 0666); if (shm_fd == -1) { cm_msg(MERROR, "frontend_init", "Failed to open shared memory: %s", strerror(errno)); return FE_ERR_HW; } // Map shared memory shm_ptr = mmap(0, SHM_DATA_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0); if (shm_ptr == MAP_FAILED) { cm_msg(MERROR, "frontend_init", "Failed to map shared memory: %s", strerror(errno)); close(shm_fd); return FE_ERR_HW; } close(shm_fd); // Close the file descriptor; it's no longer needed // Open semaphore semaphore = sem_open(SEM_NAME, 0); if (semaphore == SEM_FAILED) { cm_msg(MERROR, "frontend_init", "Failed to open semaphore: %s", strerror(errno)); munmap(shm_ptr, SHM_DATA_SIZE); return FE_ERR_HW; } return SUCCESS; } /*-- Frontend Exit -------------------------------------------------*/ INT frontend_exit() { // Unmap shared memory if (shm_ptr != nullptr) { munmap(shm_ptr, SHM_DATA_SIZE); } // Close semaphore if (semaphore != nullptr) { sem_close(semaphore); } return SUCCESS; } /*-- Begin of Run --------------------------------------------------*/ INT begin_of_run(INT run_number, char *error) { return SUCCESS; } /*-- End of Run ----------------------------------------------------*/ INT end_of_run(INT run_number, char *error) { return SUCCESS; } /*-- Pause Run -----------------------------------------------------*/ INT pause_run(INT run_number, char *error) { return SUCCESS; } /*-- Resume Run ----------------------------------------------------*/ INT resume_run(INT run_number, char *error) { return SUCCESS; } /*-- Frontend Loop -------------------------------------------------*/ INT frontend_loop() { // No action needed here return SUCCESS; } /*-- Event readout -------------------------------------------------*/ INT poll_event(INT source, INT count, BOOL test) { if (test) { // For testing, return FALSE to indicate no event return FALSE; } else { // Check if semaphore is available without blocking if (sem_trywait(semaphore) == 0) { // Semaphore acquired, event is ready return 1; // Event is available } else { if (errno == EAGAIN) { // Semaphore not available, no event ready return 0; } else { // An error occurred cm_msg(MERROR, "poll_event", "sem_trywait failed: %s", strerror(errno)); return 0; } } } } /*-- Interrupt configuration ---------------------------------------*/ INT interrupt_configure(INT cmd, INT source, POINTER_T adr) { switch (cmd) { case CMD_INTERRUPT_ENABLE: break; case CMD_INTERRUPT_DISABLE: break; case CMD_INTERRUPT_ATTACH: break; case CMD_INTERRUPT_DETACH: break; } return SUCCESS; } /*-- Trigger event routine -----------------------------------------*/ INT read_trigger_event(char *pevent, INT off) { char *pdata; // Initialize bank structure bk_init32(pevent); // Create a bank named "CR00" and specify the data type as TID_BYTE bk_create(pevent, "CR00", TID_BYTE, (void **)&pdata); // Read data from shared memory memcpy(pdata, shm_ptr, SHM_DATA_SIZE); // Close the bank pdata += SHM_DATA_SIZE; bk_close(pevent, pdata); return bk_size(pevent); }
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include "midas.h"
#include "mfe.h"
#include <curl/curl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <chrono>
#include <errno.h>
void trigger_update(INT, INT, void*);
/*-- Globals -------------------------------------------------------*/
/* The frontend name (client name) as seen by other MIDAS clients */
const char *frontend_name = "DataSimulator";
/* The frontend file name, don't change it */
const char *frontend_file_name = __FILE__;
/* frontend_loop is called periodically if this variable is TRUE */
BOOL frontend_call_loop = FALSE;
/* a frontend status page is displayed with this frequency in ms */
INT display_period = 0;
/* maximum event size produced by this frontend */
INT max_event_size = 1024 * 1024 * 32;
/* maximum event size for fragmented events (EQ_FRAGMENTED) */
INT max_event_size_frag = 5 * max_event_size;
/* buffer size to hold events */
INT event_buffer_size = 5 * max_event_size;
/*-- Shared Memory Configuration --*/
#define SHM_NAME "/my_shared_memory"
#define SEM_NAME "/my_semaphore"
#define SHM_DATA_SIZE (1024 * 1024) // 1 MB
// Shared memory and semaphore handles
int shm_fd = -1;
void* shm_ptr = nullptr;
sem_t* semaphore = nullptr;
/*-- Function declarations -----------------------------------------*/
INT frontend_init(void);
INT frontend_exit(void);
INT begin_of_run(INT run_number, char *error);
INT end_of_run(INT run_number, char *error);
INT pause_run(INT run_number, char *error);
INT resume_run(INT run_number, char *error);
INT frontend_loop(void);
INT read_trigger_event(char *pevent, INT off);
INT poll_event(INT source, INT count, BOOL test);
INT interrupt_configure(INT cmd, INT source, POINTER_T adr);
/*-- Equipment list ------------------------------------------------*/
BOOL equipment_common_overwrite = TRUE;
EQUIPMENT equipment[] = {
{"Data Simulator", /* equipment name */
{2, 0, /* event ID, trigger mask */
"SYSTEM", /* event buffer */
EQ_POLLED, /* equipment type */
0, /* event source */
"MIDAS", /* format */
TRUE, /* enabled */
RO_RUNNING, /* and update ODB */
100, /* read every 100 ms */
0, /* stop run after this event limit */
0, /* number of sub events */
TRUE, /* log history */
"", "", "",},
read_trigger_event /* readout routine */
},
{""}
};
/*-- Frontend Init -------------------------------------------------*/
INT frontend_init() {
// Open shared memory
shm_fd = shm_open(SHM_NAME, O_RDONLY, 0666);
if (shm_fd == -1) {
cm_msg(MERROR, "frontend_init", "Failed to open shared memory: %s", strerror(errno));
return FE_ERR_HW;
}
// Map shared memory
shm_ptr = mmap(0, SHM_DATA_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);
if (shm_ptr == MAP_FAILED) {
cm_msg(MERROR, "frontend_init", "Failed to map shared memory: %s", strerror(errno));
close(shm_fd);
return FE_ERR_HW;
}
close(shm_fd); // Close the file descriptor; it's no longer needed
// Open semaphore
semaphore = sem_open(SEM_NAME, 0);
if (semaphore == SEM_FAILED) {
cm_msg(MERROR, "frontend_init", "Failed to open semaphore: %s", strerror(errno));
munmap(shm_ptr, SHM_DATA_SIZE);
return FE_ERR_HW;
}
return SUCCESS;
}
/*-- Frontend Exit -------------------------------------------------*/
INT frontend_exit() {
// Unmap shared memory
if (shm_ptr != nullptr) {
munmap(shm_ptr, SHM_DATA_SIZE);
}
// Close semaphore
if (semaphore != nullptr) {
sem_close(semaphore);
}
return SUCCESS;
}
/*-- Begin of Run --------------------------------------------------*/
INT begin_of_run(INT run_number, char *error) {
return SUCCESS;
}
/*-- End of Run ----------------------------------------------------*/
INT end_of_run(INT run_number, char *error) {
return SUCCESS;
}
/*-- Pause Run -----------------------------------------------------*/
INT pause_run(INT run_number, char *error) {
return SUCCESS;
}
/*-- Resume Run ----------------------------------------------------*/
INT resume_run(INT run_number, char *error) {
return SUCCESS;
}
/*-- Frontend Loop -------------------------------------------------*/
INT frontend_loop() {
// No action needed here
return SUCCESS;
}
/*-- Event readout -------------------------------------------------*/
INT poll_event(INT source, INT count, BOOL test) {
if (test) {
// For testing, return FALSE to indicate no event
return FALSE;
} else {
// Check if semaphore is available without blocking
if (sem_trywait(semaphore) == 0) {
// Semaphore acquired, event is ready
return 1; // Event is available
} else {
if (errno == EAGAIN) {
// Semaphore not available, no event ready
return 0;
} else {
// An error occurred
cm_msg(MERROR, "poll_event", "sem_trywait failed: %s", strerror(errno));
return 0;
}
}
}
}
/*-- Interrupt configuration ---------------------------------------*/
INT interrupt_configure(INT cmd, INT source, POINTER_T adr) {
switch (cmd) {
case CMD_INTERRUPT_ENABLE:
break;
case CMD_INTERRUPT_DISABLE:
break;
case CMD_INTERRUPT_ATTACH:
break;
case CMD_INTERRUPT_DETACH:
break;
}
return SUCCESS;
}
/*-- Trigger event routine -----------------------------------------*/
INT read_trigger_event(char *pevent, INT off) {
char *pdata;
// Initialize bank structure
bk_init32(pevent);
// Create a bank named "CR00" and specify the data type as TID_BYTE
bk_create(pevent, "CR00", TID_BYTE, (void **)&pdata);
// Read data from shared memory
memcpy(pdata, shm_ptr, SHM_DATA_SIZE);
// Close the bank
pdata += SHM_DATA_SIZE;
bk_close(pevent, pdata);
return bk_size(pevent);
}
And this seems to go significantly faster (~10x faster) than the python frontend solution:
I wanted to further investigate the difference between my C++ Library and the Xilinx DMA tools perfromance, so I made some plots of sequentual tests:
Custom C++ Library, 256KB transfer, 1,000,000 samples:
Xilinx DMA tools, 256KB transfer, 100,000 samples:
Custom C++ Library, 32MB transfer, 1,000 samples:
Xilinx DMA tools, 32MB transfer, 1,000 samples:
I also smoothed the "ticker" plots and animated them to see the whole data set. I can't put the animation here, but I noticed "average down time" where there are long periods of slowdown on average visible in the plots below. There are also sharp peaks of downtime, visible in the plots above.
I made a mistake in my first attempt at the shared memory buffer. The C++ script would just read whatever was in the buffer, with no concern if it was a new data or not. This meant the python script could not bottleneck the rate. I made edits so that the C++ script would not read the same data multiple times by adding a "sequence number" header to the data packets:
data_backend.py
import mmap import posix_ipc import struct import numpy as np import time SHM_NAME = "/my_shared_memory" SEM_NAME = "/my_semaphore" SHM_DATA_SIZE = 1024 * 1024 # 1 MB HEADER_FORMAT = 'Q' # Unsigned long long (8 bytes) # Calculate total size: header + data TOTAL_SIZE = struct.calcsize(HEADER_FORMAT) + SHM_DATA_SIZE # Create or open shared memory shared_mem = posix_ipc.SharedMemory(SHM_NAME, posix_ipc.O_CREAT, size=TOTAL_SIZE) memory = mmap.mmap(shared_mem.fd, shared_mem.size) shared_mem.close_fd() # Create or open semaphore semaphore = posix_ipc.Semaphore(SEM_NAME, posix_ipc.O_CREAT, initial_value=0) sequence_number = 0 try: while True: # Increment the sequence number sequence_number += 1 # Generate or retrieve data data = np.random.randint(0, 256, size=SHM_DATA_SIZE, dtype=np.uint8) byte_data = data.tobytes() # Pack the sequence number header = struct.pack(HEADER_FORMAT, sequence_number) # Write header and data to shared memory memory.seek(0) memory.write(header + byte_data) # Signal the C++ frontend semaphore.release() # Control the data generation rate as needed except KeyboardInterrupt: pass finally: # Clean up memory.close() shared_mem.unlink() semaphore.unlink()
import mmap
import posix_ipc
import struct
import numpy as np
import time
SHM_NAME = "/my_shared_memory"
SEM_NAME = "/my_semaphore"
SHM_DATA_SIZE = 1024 * 1024 # 1 MB
HEADER_FORMAT = 'Q' # Unsigned long long (8 bytes)
# Calculate total size: header + data
TOTAL_SIZE = struct.calcsize(HEADER_FORMAT) + SHM_DATA_SIZE
# Create or open shared memory
shared_mem = posix_ipc.SharedMemory(SHM_NAME, posix_ipc.O_CREAT, size=TOTAL_SIZE)
memory = mmap.mmap(shared_mem.fd, shared_mem.size)
shared_mem.close_fd()
# Create or open semaphore
semaphore = posix_ipc.Semaphore(SEM_NAME, posix_ipc.O_CREAT, initial_value=0)
sequence_number = 0
try:
while True:
# Increment the sequence number
sequence_number += 1
# Generate or retrieve data
data = np.random.randint(0, 256, size=SHM_DATA_SIZE, dtype=np.uint8)
byte_data = data.tobytes()
# Pack the sequence number
header = struct.pack(HEADER_FORMAT, sequence_number)
# Write header and data to shared memory
memory.seek(0)
memory.write(header + byte_data)
# Signal the C++ frontend
semaphore.release()
# Control the data generation rate as needed
except KeyboardInterrupt:
pass
finally:
# Clean up
memory.close()
shared_mem.unlink()
semaphore.unlink()
frontend.cxx
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <cstdint> #include "midas.h" #include "mfe.h" #include <curl/curl.h> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <semaphore.h> #include <chrono> #include <errno.h> void trigger_update(INT, INT, void*); /*-- Globals -------------------------------------------------------*/ /* The frontend name (client name) as seen by other MIDAS clients */ const char *frontend_name = "DataSimulator"; /* The frontend file name, don't change it */ const char *frontend_file_name = __FILE__; /* frontend_loop is called periodically if this variable is TRUE */ BOOL frontend_call_loop = FALSE; /* a frontend status page is displayed with this frequency in ms */ INT display_period = 0; /* maximum event size produced by this frontend */ INT max_event_size = 1024 * 1024 * 32; /* maximum event size for fragmented events (EQ_FRAGMENTED) */ INT max_event_size_frag = 5 * max_event_size; /* buffer size to hold events */ INT event_buffer_size = 5 * max_event_size; /*-- Shared Memory Configuration --*/ #define SHM_NAME "/my_shared_memory" #define SEM_NAME "/my_semaphore" #define SHM_DATA_SIZE (1024 * 1024) // 1 MB // Total size: header + data #define TOTAL_SIZE (sizeof(uint64_t) + SHM_DATA_SIZE) // Shared memory and semaphore handles int shm_fd = -1; void* shm_ptr = nullptr; sem_t* semaphore = nullptr; /*-- Function declarations -----------------------------------------*/ INT frontend_init(void); INT frontend_exit(void); INT begin_of_run(INT run_number, char *error); INT end_of_run(INT run_number, char *error); INT pause_run(INT run_number, char *error); INT resume_run(INT run_number, char *error); INT frontend_loop(void); INT read_trigger_event(char *pevent, INT off); INT poll_event(INT source, INT count, BOOL test); INT interrupt_configure(INT cmd, INT source, POINTER_T adr); /*-- Equipment list ------------------------------------------------*/ BOOL equipment_common_overwrite = TRUE; EQUIPMENT equipment[] = { {"Data Simulator", /* equipment name */ {2, 0, /* event ID, trigger mask */ "SYSTEM", /* event buffer */ EQ_POLLED, /* equipment type */ 0, /* event source */ "MIDAS", /* format */ TRUE, /* enabled */ RO_RUNNING, /* read when running */ 0, /* read every event (0 for ASAP) */ 0, /* stop run after this event limit */ 0, /* number of sub events */ TRUE, /* log history */ "", "", "",}, read_trigger_event /* readout routine */ }, {""} }; /*-- Global Variables ----------------------------------------------*/ uint64_t last_sequence_number = 0; /*-- Frontend Init -------------------------------------------------*/ INT frontend_init() { // Open shared memory shm_fd = shm_open(SHM_NAME, O_RDONLY, 0666); if (shm_fd == -1) { cm_msg(MERROR, "frontend_init", "Failed to open shared memory: %s", strerror(errno)); return FE_ERR_HW; } // Map shared memory shm_ptr = mmap(0, TOTAL_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0); if (shm_ptr == MAP_FAILED) { cm_msg(MERROR, "frontend_init", "Failed to map shared memory: %s", strerror(errno)); close(shm_fd); return FE_ERR_HW; } close(shm_fd); // Close the file descriptor; it's no longer needed // Open semaphore semaphore = sem_open(SEM_NAME, 0); if (semaphore == SEM_FAILED) { cm_msg(MERROR, "frontend_init", "Failed to open semaphore: %s", strerror(errno)); munmap(shm_ptr, TOTAL_SIZE); return FE_ERR_HW; } return SUCCESS; } /*-- Frontend Exit -------------------------------------------------*/ INT frontend_exit() { // Unmap shared memory if (shm_ptr != nullptr) { munmap(shm_ptr, TOTAL_SIZE); } // Close semaphore if (semaphore != nullptr) { sem_close(semaphore); } return SUCCESS; } /*-- Begin of Run --------------------------------------------------*/ INT begin_of_run(INT run_number, char *error) { // Reset the last sequence number last_sequence_number = 0; return SUCCESS; } /*-- End of Run ----------------------------------------------------*/ INT end_of_run(INT run_number, char *error) { return SUCCESS; } /*-- Pause Run -----------------------------------------------------*/ INT pause_run(INT run_number, char *error) { return SUCCESS; } /*-- Resume Run ----------------------------------------------------*/ INT resume_run(INT run_number, char *error) { return SUCCESS; } /*-- Frontend Loop -------------------------------------------------*/ INT frontend_loop() { // No action needed here return SUCCESS; } /*-- Event readout -------------------------------------------------*/ INT poll_event(INT source, INT count, BOOL test) { if (test) { // For testing, return FALSE to indicate no event return FALSE; } else { // Check if semaphore is available without blocking if (sem_trywait(semaphore) == 0) { // Semaphore acquired, event is ready // Read the sequence number from shared memory uint64_t sequence_number = *(reinterpret_cast<uint64_t*>(shm_ptr)); if (sequence_number != last_sequence_number) { // New data is available last_sequence_number = sequence_number; return 1; // Event is available } else { // Duplicate data, ignore return 0; } } else { if (errno == EAGAIN) { // Semaphore not available, no event ready return 0; } else { // An error occurred cm_msg(MERROR, "poll_event", "sem_trywait failed: %s", strerror(errno)); return 0; } } } } /*-- Interrupt configuration ---------------------------------------*/ INT interrupt_configure(INT cmd, INT source, POINTER_T adr) { switch (cmd) { case CMD_INTERRUPT_ENABLE: break; case CMD_INTERRUPT_DISABLE: break; case CMD_INTERRUPT_ATTACH: break; case CMD_INTERRUPT_DETACH: break; } return SUCCESS; } /*-- Trigger event routine -----------------------------------------*/ INT read_trigger_event(char *pevent, INT off) { char *pdata; // Initialize bank structure bk_init32(pevent); // Create a bank named "CR00" and specify the data type as TID_BYTE bk_create(pevent, "CR00", TID_BYTE, (void **)&pdata); // Copy data from shared memory (excluding the sequence number) memcpy(pdata, (char*)shm_ptr + sizeof(uint64_t), SHM_DATA_SIZE); // Close the bank pdata += SHM_DATA_SIZE; bk_close(pevent, pdata); return bk_size(pevent); }
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <cstdint>
#include "midas.h"
#include "mfe.h"
#include <curl/curl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <chrono>
#include <errno.h>
void trigger_update(INT, INT, void*);
/*-- Globals -------------------------------------------------------*/
/* The frontend name (client name) as seen by other MIDAS clients */
const char *frontend_name = "DataSimulator";
/* The frontend file name, don't change it */
const char *frontend_file_name = __FILE__;
/* frontend_loop is called periodically if this variable is TRUE */
BOOL frontend_call_loop = FALSE;
/* a frontend status page is displayed with this frequency in ms */
INT display_period = 0;
/* maximum event size produced by this frontend */
INT max_event_size = 1024 * 1024 * 32;
/* maximum event size for fragmented events (EQ_FRAGMENTED) */
INT max_event_size_frag = 5 * max_event_size;
/* buffer size to hold events */
INT event_buffer_size = 5 * max_event_size;
/*-- Shared Memory Configuration --*/
#define SHM_NAME "/my_shared_memory"
#define SEM_NAME "/my_semaphore"
#define SHM_DATA_SIZE (1024 * 1024) // 1 MB
// Total size: header + data
#define TOTAL_SIZE (sizeof(uint64_t) + SHM_DATA_SIZE)
// Shared memory and semaphore handles
int shm_fd = -1;
void* shm_ptr = nullptr;
sem_t* semaphore = nullptr;
/*-- Function declarations -----------------------------------------*/
INT frontend_init(void);
INT frontend_exit(void);
INT begin_of_run(INT run_number, char *error);
INT end_of_run(INT run_number, char *error);
INT pause_run(INT run_number, char *error);
INT resume_run(INT run_number, char *error);
INT frontend_loop(void);
INT read_trigger_event(char *pevent, INT off);
INT poll_event(INT source, INT count, BOOL test);
INT interrupt_configure(INT cmd, INT source, POINTER_T adr);
/*-- Equipment list ------------------------------------------------*/
BOOL equipment_common_overwrite = TRUE;
EQUIPMENT equipment[] = {
{"Data Simulator", /* equipment name */
{2, 0, /* event ID, trigger mask */
"SYSTEM", /* event buffer */
EQ_POLLED, /* equipment type */
0, /* event source */
"MIDAS", /* format */
TRUE, /* enabled */
RO_RUNNING, /* read when running */
0, /* read every event (0 for ASAP) */
0, /* stop run after this event limit */
0, /* number of sub events */
TRUE, /* log history */
"", "", "",},
read_trigger_event /* readout routine */
},
{""}
};
/*-- Global Variables ----------------------------------------------*/
uint64_t last_sequence_number = 0;
/*-- Frontend Init -------------------------------------------------*/
INT frontend_init() {
// Open shared memory
shm_fd = shm_open(SHM_NAME, O_RDONLY, 0666);
if (shm_fd == -1) {
cm_msg(MERROR, "frontend_init", "Failed to open shared memory: %s", strerror(errno));
return FE_ERR_HW;
}
// Map shared memory
shm_ptr = mmap(0, TOTAL_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);
if (shm_ptr == MAP_FAILED) {
cm_msg(MERROR, "frontend_init", "Failed to map shared memory: %s", strerror(errno));
close(shm_fd);
return FE_ERR_HW;
}
close(shm_fd); // Close the file descriptor; it's no longer needed
// Open semaphore
semaphore = sem_open(SEM_NAME, 0);
if (semaphore == SEM_FAILED) {
cm_msg(MERROR, "frontend_init", "Failed to open semaphore: %s", strerror(errno));
munmap(shm_ptr, TOTAL_SIZE);
return FE_ERR_HW;
}
return SUCCESS;
}
/*-- Frontend Exit -------------------------------------------------*/
INT frontend_exit() {
// Unmap shared memory
if (shm_ptr != nullptr) {
munmap(shm_ptr, TOTAL_SIZE);
}
// Close semaphore
if (semaphore != nullptr) {
sem_close(semaphore);
}
return SUCCESS;
}
/*-- Begin of Run --------------------------------------------------*/
INT begin_of_run(INT run_number, char *error) {
// Reset the last sequence number
last_sequence_number = 0;
return SUCCESS;
}
/*-- End of Run ----------------------------------------------------*/
INT end_of_run(INT run_number, char *error) {
return SUCCESS;
}
/*-- Pause Run -----------------------------------------------------*/
INT pause_run(INT run_number, char *error) {
return SUCCESS;
}
/*-- Resume Run ----------------------------------------------------*/
INT resume_run(INT run_number, char *error) {
return SUCCESS;
}
/*-- Frontend Loop -------------------------------------------------*/
INT frontend_loop() {
// No action needed here
return SUCCESS;
}
/*-- Event readout -------------------------------------------------*/
INT poll_event(INT source, INT count, BOOL test) {
if (test) {
// For testing, return FALSE to indicate no event
return FALSE;
} else {
// Check if semaphore is available without blocking
if (sem_trywait(semaphore) == 0) {
// Semaphore acquired, event is ready
// Read the sequence number from shared memory
uint64_t sequence_number = *(reinterpret_cast<uint64_t*>(shm_ptr));
if (sequence_number != last_sequence_number) {
// New data is available
last_sequence_number = sequence_number;
return 1; // Event is available
} else {
// Duplicate data, ignore
return 0;
}
} else {
if (errno == EAGAIN) {
// Semaphore not available, no event ready
return 0;
} else {
// An error occurred
cm_msg(MERROR, "poll_event", "sem_trywait failed: %s", strerror(errno));
return 0;
}
}
}
}
/*-- Interrupt configuration ---------------------------------------*/
INT interrupt_configure(INT cmd, INT source, POINTER_T adr) {
switch (cmd) {
case CMD_INTERRUPT_ENABLE:
break;
case CMD_INTERRUPT_DISABLE:
break;
case CMD_INTERRUPT_ATTACH:
break;
case CMD_INTERRUPT_DETACH:
break;
}
return SUCCESS;
}
/*-- Trigger event routine -----------------------------------------*/
INT read_trigger_event(char *pevent, INT off) {
char *pdata;
// Initialize bank structure
bk_init32(pevent);
// Create a bank named "CR00" and specify the data type as TID_BYTE
bk_create(pevent, "CR00", TID_BYTE, (void **)&pdata);
// Copy data from shared memory (excluding the sequence number)
memcpy(pdata, (char*)shm_ptr + sizeof(uint64_t), SHM_DATA_SIZE);
// Close the bank
pdata += SHM_DATA_SIZE;
bk_close(pevent, pdata);
return bk_size(pevent);
}
I tried a few things to get the "correct shape" of the transfer rate curve using that we see using Xilinx XDMA tools (as seen in the figure below)
1. Writing the script solely in C
I made a few more testing smaller things:
second data set:
100 trials per data point:
500 trials per data point, adjusted makefile compiler flags to match Xilinx's makefile:
100 trials per data point, a delay added in the code to allow for "recovery":
2. Optimizing the C++ library
I made a branch of my C++ Library that "optimizes" the code a bit; instead using pointers to avoid any sort of overhead. However, I still did not see the expected "shape"